#!/usr/bin/env python
'''
eallert.py: Client application to send allerts off climatic related occurrences.

Author: Ramiro Batista da Luz
EMail: ramiroluz@gmail.com

Sends an allert to the E-Allert Web Service, can send a location and picture off the
scene.
'''
import appuifw, e32, graphics
import urllib, os, positioning
import httplib, base64
import time
import camera
from key_codes import *
#import key_codes

import sys
sys.path.append('e:\\python')

class Keyboard(object):
    def __init__(self,onevent=lambda:None):
        self._keyboard_state={}
        self._downs={}
        self._onevent=onevent
    def handle_event(self,event):
        if event['type'] == appuifw.EEventKeyDown:
            code=event['scancode']
            if not self.is_down(code):
                self._downs[code]=self._downs.get(code,0)+1
            self._keyboard_state[code]=1
        elif event['type'] == appuifw.EEventKeyUp:
            self._keyboard_state[event['scancode']]=0
        self._onevent()

    def is_down(self,scancode):
        return self._keyboard_state.get(scancode,0)

    def pressed(self,scancode):
        if self._downs.get(scancode,0):
            self._downs[scancode]-=1
            return True
        return False

CATEGORY_CHOICES = [
    u'Lost',
    u'Accident',
    u'Road block',
    u'Earthquake',
    u'Eruption',
    u'Extreme heat',
    u'Floods',
    u'Hurricanes',
    u'Landslide',
    u'Tornadoes',
    u'Tsunamis',
    u'Volcanoes',
    u'Wildfire',
    u'Winter storm',
    u'Snow',
    u'Attack',
    u'Hunger',
  ]
DEFAULT_CATEGORY = u'Lost'

SPECIAL_CARE_CHOICES = [
    u'Cancer',
    u'Deafness',
    u'Diabetes',
    u'Disabilities',
    u'Blood Disordes',
    u'High Blood Pressure',
  ]

DEFAULT_SPECIAL_CARE = u'Disabilities'

# Default interval in seconds that the gps handler will be called.
DEFAULT_INTERVAL = 5

DEFAULT_SERVER = 'eallert.appspot.com'
# Port defaults to the AppEngine local server port.
DEFAULT_PORT = '80'

DEFAULT_DATE = time.strftime('%Y-%M-%d')

if e32.in_emulator():
    DEFAULT_DRIVE = u"C:"
else:
    DEFAULT_DRIVE = u"E:"
    
IMG_DIR = u"%(drive)s\\Data\\eAllert" % {'drive':DEFAULT_DRIVE}

if not os.path.isdir(IMG_DIR):
    os.makedirs(IMG_DIR)

IMG_FILE = u"%(dir)s\\eallert.jpg" % {'dir':IMG_DIR}

if not os.path.isfile(IMG_FILE):
    from shutil import copyfile as cp
    cp('%(drive)s\\python\\eallert.jpg' % {'drive':DEFAULT_DRIVE},
       IMG_FILE)

class EAllertClient:
    def __init__(self, *args, **kwargs):
        '''
        Initializes the interface and some properties used in the system.
        '''
        self.camera_active = False
        server = kwargs.get('server')
        if server:
            self.server = server
        else:
            self.server = DEFAULT_SERVER 

        port = kwargs.get('port')
        if port:
            self.port = port
        else:
            self.port = DEFAULT_PORT

        course = kwargs.get('course')
        if course:
            self.course = course
        else: 
            self.course = None

        # GEO_POS has precedence over lat, lon coordinates.
        # If geo_pos is passed as argument, lat and lon are
        # set based on it. 
        geo_pos = kwargs.get('geo_pos')
        if geo_pos:
            lat = float(geo_pos.split(',')[0][1:])
            lon = float(geo_pos.split(',')[1][:-1])
        else:
            lat = kwargs.get('lat')
            lon = kwargs.get('lon')

        if lat:
            self.latitude = float(lat)
        else:
            self.latitude = None

        if lon:
            self.longitude = float(lon)
        else:
            self.longitude = None
                
        if self.latitude and self.longitude:
            self.setGeoPos()
        else:
            self.geo_pos = None

        # Callback interval associated with the GPS handler.
        interval = kwargs.get('interval')
        if interval:
            self.seconds_interval = interval
        else:
            self.seconds_interval = DEFAULT_INTERVAL

        picture_path = kwargs.get('picture_path')
        if picture_path:
            self.picture_path = picture_path
        else:
            self.picture_path = IMG_FILE
##            if os.path.isfile(IMG_FILE):
##                self.picture_path = IMG_FILE
##            else:
##                self.picture_path = None

        picture_data = kwargs.get('picture_data')
        if picture_data:
            self.picture_data = picture_data
        else:
            self.picture_data = None

        sender = kwargs.get('sender')
        if sender:
            self.sender = sender
        else:
            self.sender = None

        description = kwargs.get('description')
        if description:
            self.description = description
        else:
            self.description = None

        contact = kwargs.get('contact')
        if contact:
            self.contact = contact
        else:
            self.contact = None

        thumb = kwargs.get('thumb')
        if thumb:
            self.thumb = thumb
        else:
            self.thumb = None

        category = kwargs.get('category')
        if category:
            self.category = category
        else:
            self.category = DEFAULT_CATEGORY

        specialcare = kwargs.get('specialcare')
        if specialcare:
            self.specialcare = specialcare
        else:
            self.specialcare = DEFAULT_SPECIAL_CARE

        date = kwargs.get('date')
        if date:
            self.date = date
        else:
            self.date = DEFAULT_DATE

        # FLAG, used by the application to control the execution.
        self.running = 1

        # A keyboard object to handle the app keyboard events.
        self.keyboard=Keyboard()

        bg = kwargs.get('bg')
        if bg and os.path.exists(bg):
            self.bg = graphics.Image.open(bg)
        else:
            self.bg = None
            
        # Create a canvas object
        self.canvas=appuifw.Canvas(event_callback=self.keyboard.handle_event,
                                   redraw_callback=self.handle_redraw)

        appuifw.app.body=self.canvas

        appuifw.app.screen = 'full'
        self.resolution = '%dx%d' % self.canvas.size

        # Choices used as the main menu.
        self.choices=[
            (u"Send", self.handleSend),
            (u"Take a photo", self.handlePhoto),
            (u"Update location", self.handleGPS),
            (u"Options", self.handleOptions)]
        
        if self.bg == None:
            self.createBG()
            
        # Exit handler.
        appuifw.app.exit_key_handler=self.quit
        appuifw.app.title = u"EAllert Client"
        appuifw.app.menu = self.choices

    def createBG(self):
            size = self.canvas.size
            self.bg = graphics.Image.new(size)
            bar = graphics.Image.new((size[0],35,))
            bar.rectangle((0,0,bar.size[0],bar.size[1]),fill=255)
            bar.text((3,20),unicode('Menu'),fill=0xFFFFFF)
            bar.text((size[0]-50,20),unicode('Back'),fill=0xFFFFFF)
            self.bg.blit(bar,target=(0,size[1]-35))

    def setGeoPos(self):
        self.geo_pos = '%f,%f' % (self.latitude,self.longitude)
        
    def setRequestors(self):
        # at least one requestor must be set before requesting the
        # current position or last position.
        # the last requestor must always be service requestor
        # (whether or not there are other requestors).
        positioning.set_requestors([{"type":"service",
                                     "format":"application",
                                     "data":"gpsMaps_app"}])        
    def finder_cb(self, img):
        self.canvas.blit(img)

    def handlePhoto(self):
        self.camera_active = True
        camera.start_finder(self.finder_cb)
        self.canvas.bind(EKeySelect, take_photo)

    def take_photo():
        self.canvas.bind(EKeySelect, None)
        camera.stop_finder()
        
        appuifw.note(u"Taking the photo!")
        image = camera.take_photo(size = (640, 480))

        s = self.canvas.size
        #self.canvas.blit(image,target=(0,0,s[0],(s[0]/4*3)), scale=1)
        self.canvas.blit(image, scale=1)
        appuifw.note(u"Saving to %s..."% IMG_FILE)
        
        image.save(IMG_FILE)
        if os.path.isfile(IMG_FILE):
            self.picture_path = IMG_FILE
            appuifw.note(u'Photo saved!!!')
        else:
            self.picture_path = None
            appuifw.note(u'Photo NOT saved!!!', "error")
        self.camera_active = False

    def handle_redraw(self,rect):
        '''
        Handle application screen atualization. 
        '''
        if self.bg and (self.camera_active == False):
            self.canvas.blit(self.bg,scale=0)
            
    def handle_keyboard(self):
        pass

    def handleGPS(self):
        '''
        Show the map for the location informed by the device. 
        Must be tested in a moving device.
        '''
        self.updateGPSPosition()

    def updateGPSPosition(self):
        '''
        Activates the positioning callback, set position atribute with the
        GPS position.
        '''
        self.position = positioning.position()
        self.GPS_refresh(self.position)
        

    def stopGPSPosition(self):
        positioning.stop_position()

    def GPS_refresh(self, event):
        '''
        Refresh the position.
        '''
        self.position = event
        self.latitude = self.position['position']['latitude']
        self.longitude = self.position['position']['longitude']
        self.course = self.position['course']
        self.setGeoPos()
        return event

       
    def handleSend(self):
        '''
        Show a form .
        '''
        data = [(u'Sender','text',unicode(self.sender)),
        (u'Description','text',unicode(self.description)),
        (u'Contact','text',unicode(self.contact)),
        (u'Picture path','text',unicode(self.picture_path)),
        (u'Position','text', unicode(self.geo_pos)),
        (u'Category','combo',(CATEGORY_CHOICES,CATEGORY_CHOICES.index(self.category))),
        (u'Special Care','combo',(SPECIAL_CARE_CHOICES,SPECIAL_CARE_CHOICES.index(self.specialcare))),
        (u'Date','date',time.time()),]
        
        flags = appuifw.FFormEditModeOnly
        self.f_options = appuifw.Form(data, flags)
        self.f_options.save_hook=self.send
        self.f_options.execute()

    def send(self, data):
        '''
        Callback for the form save method.
        '''
        self.sender = data[0][2]
        self.description = data[1][2]
        self.contact = data[2][2]
        self.picture_path = data[3][2]
        self.geo_pos = data[4][2]
        comboIndex = data[5][2][1]
        self.category = data[5][2][0][comboIndex]
        comboIndex = data[6][2][1]
        self.specialcare = data[6][2][0][comboIndex]
        self.date = data[7][2]
        self.sendAllert()
        
    def sendAllert(self):
        form_data = {}
        form_data['sender'] = switchNewLine(self.sender).encode('utf-8')
        form_data['description'] = switchNewLine(self.description).encode('utf-8')
        form_data['contact'] = switchNewLine(self.contact).encode('utf-8')
        form_data['geo_pos'] = switchNewLine(self.geo_pos)
        form_data['category'] = self.category
        form_data['specialcare'] = self.specialcare
        form_data['date'] = self.date

        #form_data['img'] = os.path.split(self.picture_path)[1]
        data = file(self.picture_path, 'rb').read()
        self.picture_data = base64.encodestring( data )
        form_data['img_data'] = self.picture_data
        form_data['img'] = self.picture_data

        headers = { "Content-type": "application/x-www-form-urlencoded",
                "Accept": "text/plain",
                }
        
        params = urllib.urlencode(form_data,doseq=1)
        conn = httplib.HTTPConnection( '%s:%s' % (self.server, self.port) )
        conn.request( "POST", "/allert", params, headers )
        response = conn.getresponse( )
        # status for debugging
        
        if response.status == 302:
            appuifw.note(u'Allert sent!')
        else:
            appuifw.note(u'Allert not sent! %s' % response.status)
        conn.close( )

    def handleOptions(self):
        '''
        Show a form with zoom and resolution options, it will be used to obtain
        the map and affect the results off GPS and Find methods.
        '''
        pass
        #resolution = [u'352x416', u'416x352',  
        #              u'176x208', u'208x176', 
        #              u'240x320', u'320x240']
        #data = [(u'Zoom','number', self.zoom),
        #        (u'resolution','combo',(resolution,
        #                                resolution.index(self.resolution)))]
        #flags = appuifw.FFormEditModeOnly
        #self.f_options = appuifw.Form(data, flags)
        #self.f_options.save_hook=self.saveOptions
        #self.f_options.execute()

    def saveOptions(self, data):
        '''
        Callback for the options form save method.
        '''
        pass
        #self.zoom = data[0][2]
        #comboIndex = data[1][2][1]
        #self.resolution = data[1][2][0][comboIndex]
        
    def exit_key_handler(self):
        self.stopGPSPosition()
        self.app_lock.signal()

    def quit(self):
        positioning.stop_position()
        self.running = 0

    def run(self):
        try:
            # set requestors.
            self.setRequestors()
        except:
            appuifw.note(u'Failed to set GPS requestors!')
        
        try:
            self.updateGPSPosition()
        except:
            appuifw.note(u'Failed to update GPS position!')
        
        while self.running:
            if self.keyboard:
                self.handle_keyboard()
            self.handle_redraw(())
            e32.ao_yield()

        self.stopGPSPosition()

def switchNewLine(textField):
    '''
    Turn the new line character 8233 in a \n new line.
    
    The character 8233(represented in python by u"\u2029") is a standard 
    new line, but causes trouble with unicode and HTML. So my choice was 
    to replace it by the \n new line.
    '''
    return textField.replace(u"\u2029",chr(10))
    
eallert = EAllertClient()
eallert.run()
